#include "fdpoll.h"

#if defined(OS_SUN)
#include <sys/filio.h>
#include <sys/devpoll.h>

class FDPoll::Impl
{
public:
    Impl()
        : m_fdmap(),
          m_pollvector( NULL ),
          m_generation( 0 ),
          m_devpollfd( -1 ),
          m_pollvectorsize( 0 ),
          m_nextidx( 0 ),
          m_lastidx( 0 )
    {
        m_devpollfd = open( "/dev/poll", O_RDWR );
    }

    ~Impl() throw()
    {
        if( m_devpollfd > 0 )
            close( m_devpollfd );

        delete[] m_pollvector;
    }

    int32_t add( Fd infd, int32_t optype, void * userdata )
    {
        if( infd < 0 )
            return -1;

        if( !m_fdmap.insert( std::make_pair( infd, std::make_pair( userdata, m_generation ) ) ).second )
        {
            return -1;
        }

        int32_t ret = poll_ctrl( infd, optype, 0 );
        if( 0 != ret )
            m_fdmap.erase( infd );
        return ret;
    }

    int32_t set( Fd infd, int32_t optype, void * userdata )
    {
        if( infd < 0 )
            return -1;
        FdMap::iterator iter = m_fdmap.find( infd );
        if( m_fdmap.end() == iter )
            return -1;

        int32_t ret = poll_ctrl( infd, optype, 1 );
        if( 0 == ret )
            iter->second.first = userdata;
        return ret;
    }

    int32_t remove( Fd infd )
    {
        if( infd < 0 )
            return -1;
        FdMap::iterator iter = m_fdmap.find( infd );
        if( m_fdmap.end() == iter )
            return -1;

        int32_t ret = poll_ctrl( infd, 0, 1 );
        if( 0 == ret )
            m_fdmap.erase( infd );
        return ret;
    }

    int32_t poll(  int32_t waitmillisec )
    {
        m_nextidx = 0;
        m_lastidx = 0;
        if( m_devpollfd < 0 )
            return -1;
        int32_t eventcount = m_fdmap.size();
        if( eventcount < 1 )
            eventcount = 1;

        if( !m_pollvector || eventcount > m_pollvectorsize )
        {
            delete[] m_pollvector;
            int32_t alloccount = eventcount + 256;
            m_pollvector = new struct pollfd[alloccount];
            m_pollvectorsize = alloccount;
        }
        struct devpoll dopoll;
        dopoll.dp_fds       = m_pollvector;
        dopoll.dp_nfds      = eventcount;
        dopoll.dp_timeout   = waitmillisec;
        int32_t ret = ioctl( m_devpollfd, DP_POLL, &dopoll );
        if( ret > 0 )
            m_lastidx = ret;

        return ( ret >= 0 ? ret : ( errno > 0 ? -errno : ( errno == 0 ? -1 : errno ) ) );
    }

    int32_t next( int32_t & optype, void *& userdata )
    {
        for( ; m_nextidx < m_lastidx; ++m_nextidx )
        {
            struct pollfd & the_entry = m_pollvector[m_nextidx];
            FdMap::const_iterator iter = m_fdmap.find( the_entry.fd );
            if( iter != m_fdmap.end() && iter->second.second != m_generation )
            {
                optype = fdpoll_eventmask( the_entry.revents );
                userdata = iter->second.first;
                ++m_nextidx;
                return true;
            }
        }
        optype = 0;
        userdata = 0;
        return false;
    }

private:
    int32_t poll_ctrl( Fd infd, int32_t optype, int32_t removeflag )
    {
        struct pollfd entry[2];
        int32_t           n   = 0;
        int32_t           nwr = 0;
        if( removeflag )
        {
            entry[n].fd     = infd;
            entry[n].events = POLLREMOVE;
            entry[n].revents= 0;
            nwr = sizeof( entry[0] );
        }
        if( !removeflag || 0 != optype )
        {
            entry[n].fd     = infd;
            entry[n].events = devpoll_eventmask( optype );
            entry[n].revents= 0;
            nwr = sizeof( entry[0] );
        }
        return ( write( m_devpollfd, entry, nwr ) == nwr ? 0 : ( errno != 0 ? errno : -1 ) )
    }

    int32_t devpoll_eventmask( int32_t optype )
    {
        int32_t ret = 0;

        if( 0 != ( optype & EOPTYPE_IN ) )
            ret += POLLIN;
        if( 0 != ( optype & EOPTYPE_OUT ) )
            ret += POLLOUT;
        if( 0 != ( optype & EOPTYPE_PRI ) )
            ret += POLLPRI;
        return ret;
    }

    int32_t fdpoll_eventmask( int32_t devpoll_mask )
    {
        int32_t ret = 0;

        if( 0 != ( devpoll_mask & POLLIN ) )
            ret += EOPTYPE_IN;
        if( 0 != ( devpoll_mask & POLLOUT ) )
            ret += EOPTYPE_OUT;
        if( 0 != ( devpoll_mask & POLLPRI ) )
            ret += EOPTYPE_PRI;
        if( 0 != ( devpoll_mask & ( POLLERR | POLLNVAL ) ) )
            ret += EOPTYPE_ERROR;
        if( 0 != ( devpoll_mask & POLLHUP ) )
            ret += EOPTYPE_HUP;
        return ret;
    }

private:
    typedef std::map< Fd, std::pair<void*, size_t> > FdMap;

    FdMap           m_fdmap;
    struct pollfd * m_pollvector;
    size_t          m_generation;
    int32_t             m_devpollfd;
    int32_t             m_pollvectorsize;
    int32_t             m_nextidx;
    int32_t             m_lastidx;
};

#elif defined(OS_LINUX)

#include <sys/epoll.h>

class FDPoll::Impl
{
public:
    enum{ EMAXFDS = 32768 };

    Impl()
        : m_epollfd( epoll_create( EMAXFDS ) ),
          m_epoll_eventcount( 0 ),
          m_nexteventidx( 0 ),
          m_events( new struct epoll_event[EMAXFDS] )
    {
        if( m_epollfd < 0 && errno != 0 )
            m_epollfd = errno;
    }

    ~Impl() throw()
    {
        if( m_epollfd > 0 )
            close( m_epollfd );
        delete[] m_events;
    }

    int32_t get_maxfd() const
    {
        return EMAXFDS - 1;
    }

    int32_t add( Fd infd, int32_t optype, void * userdata )
    {
        return poll_ctrl( EPOLL_CTL_ADD, infd, optype, userdata );
    }

    int32_t set( Fd infd, int32_t optype, void * userdata )
    {
        return poll_ctrl( EPOLL_CTL_MOD, infd, optype, userdata );
    }

    int32_t remove( int32_t fd )
    {
        return poll_ctrl( EPOLL_CTL_DEL, fd, 0, 0 );
    }

    int32_t poll( int32_t waitmillisec )
    {
        int32_t event_count = EMAXFDS;
        m_epoll_eventcount = epoll_wait( m_epollfd, m_events, event_count, waitmillisec );
        m_nexteventidx = 0;
        // = 0: timeout
        return ( m_epoll_eventcount >= 0 ? m_epoll_eventcount :
            ( errno > 0 ? -errno : ( errno == 0 ? m_epoll_eventcount : errno ) ) ) ;
    }

    int32_t next( int32_t & optype, void *& userdata )
    {
        if( m_nexteventidx >= m_epoll_eventcount )
            return -1;

        optype = fdpoll_eventmask( m_events[m_nexteventidx].events );
        userdata = m_events[m_nexteventidx].data.ptr;
        ++m_nexteventidx;
        return 0;
    }

private:
    int32_t poll_ctrl( int32_t epoll_op, Fd infd, int32_t optype, void * userdata )
    {
        if( infd < 0 )
            return -1;
        if( m_epollfd < 0 )
            return m_epollfd;

        struct epoll_event poll_event;
        memset( &poll_event, 0, sizeof( struct epoll_event ) );
        poll_event.data.ptr = userdata;
        poll_event.events = epoll_eventmask( optype );
        return epoll_ctl( m_epollfd, epoll_op, infd, &poll_event ) ? errno : 0;
    }

    int32_t epoll_eventmask( int32_t optype )
    {
        int32_t ret = 0;
        if( 0 != ( optype & EOPTYPE_IN ) )
            ret |= EPOLLIN;
        if( 0 != ( optype & EOPTYPE_OUT ) )
            ret |= EPOLLOUT;
        if( 0 != ( optype & EOPTYPE_PRI ) )
            ret |= EPOLLPRI;
        return ret;
    }

    int32_t fdpoll_eventmask( int32_t epoll_mask )
    {
        int32_t ret = 0;
        if( 0 != ( epoll_mask & EPOLLIN ) )
            ret |= EOPTYPE_IN;
        if( 0 != ( epoll_mask & EPOLLOUT ) )
            ret |= EOPTYPE_OUT;
        if( 0 != ( epoll_mask & EPOLLPRI ) )
            ret |= EOPTYPE_PRI;
        if( 0 != ( epoll_mask & EPOLLERR ) )
            ret |= EOPTYPE_ERROR;
        if( 0 != ( epoll_mask & EPOLLHUP ) )
            ret |= EOPTYPE_HUP;
        return ret;
    }

private:
    int32_t     m_epollfd;
    int32_t     m_epoll_eventcount;
    int32_t     m_nexteventidx;
    struct epoll_event * m_events;
};

#else
    # error Please add support for your platform in platform.h
#endif

FDPoll::FDPoll()
    : m_impl( new FDPoll::Impl() )
{}

FDPoll::~FDPoll() throw()
{
    delete m_impl;
}

int32_t FDPoll::get_maxfd() const
{
    return m_impl->get_maxfd();
}

int32_t FDPoll::add( Fd infd, int32_t optype, void * userdata )
{
    return m_impl->add( infd, optype, userdata );
}

int32_t FDPoll::set( Fd infd, int32_t optype, void * userdata )
{
    return m_impl->set( infd, optype, userdata );
}

int32_t FDPoll::remove( int32_t fd )
{
    return m_impl->remove( fd );
}

int32_t FDPoll::poll( int32_t waitmillisec )
{
    return m_impl->poll( waitmillisec );
}

int32_t FDPoll::next( int32_t & optype, void *& userdata )
{
    return m_impl->next( optype, userdata );
}



